/* Copyright 2024 Coremail
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import type { EmailAddress, IBuffer, IFolder, IMail, MailAttribute, MailStructure, Mime, Properties } from 'coremail'
import { CMError, FolderType, getLogger, MailEngine, showDebugLog } from 'coremail'
import type common from '@ohos.app.ability.common'
import { FileManager } from '../../../../base/src/main/ets/utils/FileManger'
import { StringUtil } from '../../../../base/src/main/ets/utils/StringUtil'
import { NoticeManager } from '../../../../base/src/main/ets/utils/NoticeManager'
import { AttachmentUtil } from '../../../../base/src/main/ets/utils/AttachmentUtil'
import { AccountPreference } from '../../../../base/src/main/ets/store/AccountPreference'
// import { TLSTest } from './test'
// import { TcpSocketTest } from "./test2"
import { ErrorUtil } from './utils/ErrorUtil'
import { appStorage, DataStorage } from './datastorage'
import { BaseConstants } from '@ohos/base/src/main/ets/constants/BaseConstants'

import { EmlParser } from 'coremail/src/main/ets/format/eml'
import { FileBuffer } from '@ohos/base/src/main/ets/basedata/FileBuffer'
import { EmlFileManager } from '@ohos/base/src/main/ets/basedata/EmlFileManager'
import { getFileName, guessContentType, MemBuffer } from 'coremail/src/main/ets/utils/file_stream'
import { CAUtil } from './utils/CAUtil'
import { MailStorage } from '@ohos/base/src/main/ets/store/MailStorage'
import { ParseUtil } from '@ohos/base/src/main/ets/utils/ParseUtil'
import { SyncStore } from '@ohos/base/src/main/ets/store/sync_store'
import { getMimePartName, MimeMail, serializeMail } from 'coremail/src/main/ets/utils/mime'

import { MsgReader } from 'coremail/Index'
import fs from '@ohos.file.fs'
import { getTaskLogger } from '@ohos/base/src/main/ets/utils/LogUtil'
import util from '@ohos.util'
import { Folder } from 'coremail/src/main/ets/engine/folder'

export { getLogger } from "coremail"

// 开启调试日志
showDebugLog(true);
const taskLogger = getTaskLogger("InitFolder");
const unreadLogger = getTaskLogger("__unreadCount");
const draftLogger = getTaskLogger("__draft");
const logger = getLogger("app");

type ServerConfig = {
  host: string,
  port: number,
  isSSL: number
}

type ServerType = 'pop3' | 'imap' | 'smtp'

export class Account {
  id: string = '';
  email: string = '';
  password: string = '';
  serverConfig: {
    pop3?: ServerConfig;
    imap?: ServerConfig;
    smtp?: ServerConfig;
  } = {};
  openSign: boolean = false
  sign ?: string = ''

  constructor() {
  }
}

class AccountManager {
  accounts: Account[] = [];

  constructor() {
  }
}

function idString(idMap: Map<string, string>): string {
  const s = [];
  for (const [key, value] of idMap) {
    s.push(key);
    s.push(value);
  }
  return `"${s.join('" "')}"`;
}

async function getPropertiesFromAccount(data: Account, context: common.Context): Promise<Properties> {
  let prop: Properties = {
    smtp: {
      host: data.serverConfig.smtp.host,
      port: data.serverConfig.smtp.port,
      secure: data.serverConfig.smtp.isSSL > 0
    },
    userInfo: { username: data.email, password: data.password },
    extra: { "imap.id": idString(new Map([
      ["name", "mailclient"],
      ["version", "1.0.0"],
      ["vendor", "coremail"],
      ["support-url", "support"],
    ])) },
  }
  if (data.serverConfig.pop3) {
    prop.pop3 = {
      host: data.serverConfig.pop3.host,
      port: data.serverConfig.pop3.port,
      secure: data.serverConfig.pop3.isSSL > 0
    }
  } else if (data.serverConfig.imap) {
    prop.imap = {
      host: data.serverConfig.imap.host,
      port: data.serverConfig.imap.port,
      secure: data.serverConfig.imap.isSSL > 0
    }
  }
  let ca: string[] = []
  if (prop.smtp.secure || prop.imap?.secure || prop.pop3?.secure) {
    ca = await CAUtil.getCA(context, data.email)
  }
  prop.ca = ca
  return prop;
}


async function getAccountEngine(data: Account, context: common.Context): Promise<MailEngine> {
  let storage = new MailStorage(context, data.email);
  const engine = new MailEngine();
  let prop: Properties = await getPropertiesFromAccount(data, context);
  const store = new SyncStore(storage);
  store.setBufferCreator(storage.getBufferCreator());
  engine.registerStore(store);
  engine.setProperties(prop);
  return engine;
}

export type FolderItem = {
  name: string;
  unreadCount: number;
  type: FolderType;
  subFolders: FolderItem[];
}


async function getFolderData(folder: IFolder,needRefresh = false): Promise<FolderItem> {
  taskLogger.trace("[app/getFolderData]get folder data", folder.getFullName())
  let unreadCount = 0;
  if (folder.getType() != FolderType.root) {
    try {
      if(needRefresh) {
        await folder.refresh();
      }
      unreadCount = await folder.getUnreadMailCount();
    }
    catch (e:unknown) {
      taskLogger.trace("[app/getFolderData] get folder data failed", folder.getFullName())
    }
  }
  const f: FolderItem = {
    name: folder.getFullName(),
    unreadCount: unreadCount,
    type: folder.getType(),
    subFolders: []
  }
  const folders = await folder.getSubFolders();
  f.subFolders = await Promise.all(folders.map(f => getFolderData(f,needRefresh)))
  logger.info('folder data', f.name, f.subFolders)
  return f;
}

type SecureType = 0 | 1 | 2;
type ReceiveProtocol = "imap" | "pop3"
type SendProtocol = "smtp"

type FolderItemViewModel = {
  name: string;
  subFolders: FolderItemViewModel[];
  unreadCount: number;
  type: FolderType;
}

export type AccountFolder = {
  account: string;
  rootFolder: FolderItem;
}

export type MailSummary = {
  mid: string;
  account: string;
  subject: string;
  summary: string;
  date: Date;
  isFlagged: boolean;
  isNew: boolean;
  hasAttachment: boolean;
  folder: string;
}

export type MailAddress = {
  name: string | null;
  email: string;
}

export type InlineAttachment = {
  file: string,
  contentId: string
}

export type Attachment = {
  name: string;
  type: string;
  subType: string;
  id: string;
  encoding: string;
  size: number;
  index: number;
  localPath: string | null;
}

export type MailItem = {
  from: MailAddress;
  to: MailAddress[];
  cc: MailAddress[];
  bcc: MailAddress[];
  subject: string;
  date: Date;
  html: string;
  text: string;
  size: number;
  attachments: Attachment[];
  inlineImages: Attachment[];
  isFlagged: boolean;
}

interface IApp {
  /**
   *
   * @param context 上下文
   * @param email 输入登录邮箱账号
   * @returns 当前账号是否已登录
   */
  checkRepeatAccount(email: string): Promise<boolean>

  /**
   *
   * 登录连接服务器
   * @param user 邮箱地址
   * @param pass 密码
   * @param host 服务器地址
   * @param port 端口
   * @param secureType 加密安全类型
   * @param type 协议
   * @returns @description 获取添加账号登录连接结果
   */
  addAccount(user: string, pass: string, host: string, port: number, secureType: number, protocol: ReceiveProtocol,
             sendHost: string, sendPort: number, sendSecureType: number, sendProtocol: SendProtocol, isSetting: boolean): Promise<void>

  /**
   * 判断是否存在已登录账号
   * @returns 是否进入主页
   */
  canShowLoginPage(): Promise<boolean>

  /**
   * 删除账号
   * @returns
   */
  deleteAccount(email: string): Promise<void>

  /**
   * 获取账号信息
   * @param email
   * @returns
   */
  getAccountData(email: string): Promise<Account>

  /**
   * 获取指定账号下文件夹
   * @param account 账号邮箱
   * @returns 文件夹根目录
   * @description 以树结构形式返回数据
   */
  getAccountFolder(account: string): Promise<AccountFolder>;

  /**
   * 获取指定账号下指定文件夹中的邮件
   * @param account 账号邮箱
   * @param folder 文件夹名称
   * @param start 起始位置/偏移量
   * @param size 获取邮件数量
   * @param sortType 排序方式，只有两种排序方式，日期或者大小
   * @param desc 是否降序
   * @returns 返回指定数量邮件摘要信息
   */
  getMailList(account: string, folder: string, start: number, size: number, sortType: 'date' | 'size', desc: boolean): Promise<MailSummary[]>;

  /**
   * 加载指定账号下指定文件夹中的更多邮件
   * @param account 账号邮箱
   * @param folder 文件夹名称
   * @param mailId 偏移量邮件id，即最后一封邮件的id
   * @param size 获取邮件数量
   * @param sortType 排序方式，只有两种排序方式，日期或者大小
   * @param desc 是否降序
   * @returns 返回指定数量邮件摘要信息
   */
  getMailsAfter(account: string, folder: string, mailId: string, size: number, sortType: 'date' | 'size', desc: boolean): Promise<MailSummary[]>;

  /**
   * 指定账号下删除文件夹
   * @param account 账号邮箱
   * @param folder 待删除文件夹完整名称（包含父文件夹路径）
   * @returns
   */
  deleteFolder(account: string, folder: string): Promise<void>

  /**
   * 指定账号文件夹下创建新文件夹
   * @param account 账号邮箱
   * @param parentFolder 父文件夹名字(完整文件夹路径)，根文件夹传null
   * @param newFolderName 新文件夹名称
   * @returns
   */
  createFolder(account: string, parentFolder: string | null, newFolderName: string): Promise<void>

  /**
   * 指定账号文件夹重命名
   * @param account 账号邮箱
   * @param folder 当前文件夹名称(完整文件夹路径)
   * @param newName 新文件夹名称
   * @returns
   */
  renameFolder(account: string, folder: string, newName: string): Promise<void>

  refresh(account: string): Promise<void>;

  /**
   * 移动邮件
   * @param account 账号邮箱
   * @param fromFolder 待移动邮件所属文件夹
   * @param mailIdList 待移动邮件id数组
   * @param toFolder 目标文件夹
   * @returns 移动后邮件的新id数组
   */
  moveMail(account: string, fromFolder: string, mailIdList: string[], toFolder: string): Promise<string[]>

  /**
   * 删除邮件
   * @param account 账号邮箱
   * @param folder 待删除邮件所属文件夹
   * @param mailIdList 待删除邮件id数组
   * @returns
   */
  deleteMail(account: string, folder: string, mailIdList: string[]): Promise<void>

  /**
   * 指定账号文件夹下邮件更新标志位
   * @param account 账号邮箱
   * @param folder 文件夹名称
   * @param mailIdList 待更新的邮件id数组
   * @param flagType 待更新的标志位，目前只支持未读和标记
   * @param newValue 标志位新值
   * @returns
   */
  markMail(account: string, folder: string, mailIdList: string[], flagType: 'new' | 'flagged', newValue: boolean): Promise<void>

  /**
   * 获取邮件详情
   * @param account 账号邮箱
   * @param folder 文件夹名称
   * @param mailId 邮件id
   * @returns 完整邮件信息
   * @description 获取内容包括邮件大小、日期、发件人、收件人、抄送、密送、主题、内容、附件和内联附件，但不下载附件
   */
  getMail(account: string, folder: string, mailId: string): Promise<MailItem>;

  /**
   * 获取邮件的所有附件信息
   * @param account 账号邮箱
   * @param folder 文件夹名称
   * @param mailId 邮件id
   * @returns 所有附件信息
   * @description 只获取信息，如果未下载附件不会自动进行下载
   */
  getAttachments(account: string, folder: string, mailId: string): Promise<Attachment[]>;

  /**
   * 下载附件
   * @param account 账号邮箱
   * @param folder 文件夹名称
   * @param mailId 邮件id
   * @param index 附件索引
   * @param inline 是否是内联附件
   * @returns 下载完成后的本地路径
   */
  download(account: string, folder: string, mailId: string, index: number, inline: boolean): Promise<string>;

  /**
   * 获取内联附件
   * @param account 账号邮箱
   * @param folder 文件夹名称
   * @param mailId 邮件id
   * @returns 完整附件信息，包含本地路径
   * @description 如果附件未存在于本地，下载完成后获取本地路径，再返回完整附件信息
   */
  getInlineImages(account: string, folder: string, mailId: string): Promise<Attachment[]>;

  search(account: string, keyword: string, types: ['subject' | 'attachment' | 'from' | 'to'], start: number, size: number): Promise<MailSummary[]>;

  sendMail(account: string, to: MailAddress[], cc: MailAddress[],
           bcc: MailAddress[], subject: string, content: string,
           inlineImages: InlineAttachment[],
           attachments: Attachment[],
           isPlain: boolean): Promise<void>;

  saveDraft(draftId: string, account: string, to: MailAddress[], cc: MailAddress[],
            bcc: MailAddress[], subject: string, content: string,
            inlines: {
              file: string,
              contentId: string
            }[], attachments: string[],
            isPlain: boolean): Promise<string>;

  deleteDraft(draftId: string, account: string): Promise<void>;

  setIntervalToSync(interval: number): Promise<void>;

  /**
   * 同步所有账号下邮件
   * @returns
   */
  syncAllAccountMails(): Promise<void>;

  /**
   * 获取完整的邮件(包括下载内联图片和附件到本地)
   * @param account 账号邮箱
   * @param folder 文件夹
   * @param mailId 邮件id
   * @returns 完整邮件信息
   * @description 主要用于对邮件发起回复转发或再编辑前，需要下载完整的邮件内容
   */
  getFullMail(account: string, folder: string, mailId: string): Promise<MailItem>;

}

class MailApp implements IApp {
  _accountManager = new AccountManager()
  _context: common.Context | null = null;
  _engineMap: Map<string, MailEngine> = new Map;
  _mCurrentAccount: string | null = null;

  constructor() {
    // this.testTLS();
  }

  async testTLS(): Promise<void> {
    // const t = new TLSTest();
    // await t.connectTest()
  }

  setUIContext(context: common.Context): void {
    this._context = context;
  }

  /**
   *
   * @param context 上下文
   * @param email 输入登录邮箱账号
   * @returns 当前账号是否已登录
   */
  checkRepeatAccount(email: string): Promise<boolean> {
    return new AccountPreference().checkKey(this._context, email)
  }

  /**
   *
   * 登录连接服务器
   * @param user 邮箱地址
   * @param pass 密码
   * @param host 服务器地址
   * @param port 端口
   * @param secureType 加密安全类型
   * @param type 协议
   * @returns @description 获取添加账号登录连接结果
   */
  async addAccount(user: string, pass: string, host: string, port: number, secureType: number, protocol: ReceiveProtocol,
                   sendHost: string, sendPort: number, sendSecureType: number, sendProtocol: SendProtocol, isSetting: boolean): Promise<void> {
    let account = new Account()
    account.email = this.encrypt(user)
    account.password = this.encrypt(pass)
    if (protocol == "imap") {
      account.serverConfig = { imap: { isSSL: secureType, host: host, port: port } }
    } else {
      account.serverConfig = { pop3: { isSSL: secureType, host: host, port: port } }
    }
    if (sendProtocol == "smtp") {
      account.serverConfig.smtp = { isSSL: sendSecureType, host: sendHost, port: sendPort }
    }
    const interval = await this.getSyncGapInterval()
    const engine = await this.generateAccountEngine(account)
    // todo
    const store = engine.getStore()
    const transport = engine.getTransport()
    logger.info("login check store", '****' + user.substring(4));
    return store.check().then(() => {
      logger.info("login check transport", '****' + user.substring(4));
      return transport.check().then(() => {
        this._engineMap.set(user, engine)
        store.sync();
        //RegisterStore 增加账号后的RegisterStore
        // let storage = new StorageStore(this._context,account.email);
        // await storage.init();
        // engine.registerStore(storage);
        this._accountManager.accounts.push({ id: user, email: user, password: pass } as Account)
        if (!isSetting) { // 修改账号配置不变动当前账号
          DataStorage.setCurrentAccount(user)
        }
        new AccountPreference().saveData(this._context, user, JSON.stringify(account))
        // 登录默认同步周期间隔
        engine.setSyncInterval(interval > 0 ? interval : BaseConstants.SYNC_MIN_15)
      }).catch((err) => {
        logger.error("login transport failed", '****' + user.substring(4));
        let sendErr = err as CMError
        sendErr.message = sendProtocol as string
        throw sendErr
      })
    }, (err) => {
      logger.error("login store failed", '****' + user.substring(4));
      let receiveErr = err as CMError
      receiveErr.message = protocol as string
      throw receiveErr
    })
  }

  /**
   * 判断是否存在已登录账号
   * @returns 是否进入主页
   */
  async canShowLoginPage(): Promise<boolean> {
    return new AccountPreference().getAll(this._context)
      .then(async (arr: string[]) => {
        if (arr.length > 0) {
          let account: Account
          const interval = await this.getSyncGapInterval()
          for (let i = 0; i < arr.length; i++) {
            const item = arr[i]
            account = JSON.parse(item) as Account
            const engine = await this.generateAccountEngine(account);
            const store = engine.getStore();
            if (store.sync) store.sync();
            this._engineMap.set(account.email, engine);
            // 恢复默认同步周期间隔
            engine.setSyncInterval(interval > 0 ? interval : BaseConstants.SYNC_MIN_15)
            if (i == 0) {
              DataStorage.setCurrentAccount(account.email)
            }
            //RegisterStore 进入首页前为每个账号注册engine
            // let storage = new StorageStore(this._context,account.email);
            // await storage.init();
            // engine.registerStore(storage);
          }
          return false
        }
        return true
      }).catch(() => {
        return true
      })
  }

  /**
   * UI界面更新.
   * 更新当前文件夹一个mail的attr.
   * 暂时再同步时的update-mail-attr事件使用
   * @param mailId
   * @param attr
   */
  updateMail(map:Map<string,MailAttribute[]>):void {
    DataStorage.updateMailAttr(map);
  }

  /**
   * 仅仅UI中删除一封邮件.
   * 删除当前文件夹的一封邮件
   * @param mailId
   * @param attrs
   */
  deleteMailById(mailId):void {
    DataStorage.deleteMail(mailId);
  }

  private async generateAccountEngine(account: Account): Promise<MailEngine> {
    // TODO: 待定账号文件夹路径
    const accountFilePath = `${this._context.filesDir}/${account.email}`
    const engine = await getAccountEngine(account, this._context);
    engine.on("new-mail", async (folder, mails) => {
      const folderName = folder.getFullName()
      const summaries = await this.getMailSummaries(folder, mails)
      await folder.refresh();
      logger.debug(`[__pop3] got new mail ${summaries.length}`)
      DataStorage.addNewMails(account.email, folderName, summaries,true);
      for (const element of summaries) {
        NoticeManager.notify(element.account, element.subject, "")
      }
      folder.getUnreadMailCount()
        .then(unreadCount => {
          DataStorage.setStoreFolderUnread(account.email, folderName, unreadCount)
        })

      const mids = mails.map(m => m.getId())
      this.getContentSummary(account.email, folderName, mids)
    })
    engine.on("sync-interval", async (folder: string) => {
      logger.debug(`[OnSyncIntervalEvent][sync-interval][got sync event for ${folder}]`);
      if (DataStorage.currentAccount() == account.email && DataStorage.currentFolder() == folder) {
        if(DataStorage.currentDisplayMailCount() < 10) {
          this.getMailList(account.email, folder, 1, 10, "date", true);
        }
      }
    })

    //更新邮件属性.
    // let param = {
    //   folderFullName:fullName,
    //   mailId:mailId,
    //   attr:mailBasic.attributes
    // }
    engine.on("update-mail", async (param:{folderFullName:string,map:Map<string,MailAttribute[]> })=>{
      // let logger = getTaskLogger("__sync");
      let folderFullName = param.folderFullName;
      let currentFolder = DataStorage.currentFolder();
      if(currentFolder == folderFullName) {
        let map: Map<string,MailAttribute[]> = param.map;
        // logger.log(`[OnUpdateMailEvent][${folderFullName}${mailId},${attrs}]`);
        this.updateMail(map);
      }
    });


    engine.on("delete-mail", async (param:{folderFullName:string,mailId:string})=>{
      let logger = getTaskLogger("__sync");
      let folderFullName = param.folderFullName;
      let mailId = param.mailId;
      let currentFolder = DataStorage.currentFolder();
      if(currentFolder == folderFullName) {
        logger.log(`[OnDeleteMailEvent][${folderFullName}-${mailId}]`);
        this.deleteMailById(mailId);
      }
    });

    //同步结束后,更新重要文件夹未读数.
    //同步结束后,更新重要文件夹未读数.
    engine.on("sync-end", async ()=>{
      let logger = getTaskLogger("__sync");
      logger.trace(`[sync end]`)
      await this.updateAccountFolder(account.email,true);
      await this.updateImportantFolderUnread(account.email);
      await this.updateCurrentFolderCount(account.email);
    });
    return engine
  }

  /**
   * 删除账号
   * @param email
   * @returns
   */
  async deleteAccount(email: string): Promise<void> {
    await new AccountPreference().deleteData(this._context, email)
    DataStorage.deleteAccountFolders(email)
    let engine = this._engineMap.get(email);
    const store = engine.getStore();
    if (store && store.release) {
      store.release();
    }
    this._engineMap.delete(email)
  }

  /**
   * 获取账号信息
   * @param email
   * @returns
   */
  async getAccountData(email: string): Promise<Account> {
    return new AccountPreference().getValue(this._context, email)
      .then((data: string) => {
        const account: Account = JSON.parse(data) as Account
        return account
      })
  }

  async getAccountList(change: boolean = false): Promise<Account[]> {
    return new AccountPreference().getAll(this._context)
      .then((arr: string[]) => {
        if (arr.length > 0) {
          let accountList: Account[] = []
          arr.forEach((item: string) => {
            const account: Account = JSON.parse(item) as Account
            accountList.push(account)
          })
          if (change) {
            DataStorage.setCurrentAccount(accountList[0].email)
          }
          return accountList
        }
        return []
      }).catch(() => {
        return []
      })
  }

  async saveSign(data: Account): Promise<void> {
    return new AccountPreference().saveData(this._context, data.email, JSON.stringify(data))
  }

  // todo 预留
  encrypt(content: string): string {
    // content
    return content
  }

  decrypt(content: string): string {
    return content
  }

  /**
   * 获取当前账号的文件夹树.
   * 1,实际服务器返回的是FolderData,没有subFolders属性.
   * @param account
   * @returns
   */
  async getAccountFolder(account: string): Promise<AccountFolder> {
    const engine = this._engineMap.get(account)
    const root = await engine.getRootFolder();
    taskLogger.trace(`[app/getAccountFolder][get folder data is ${JSON.stringify(root.getFullName())}]`);
    const data = await getFolderData(root)
    taskLogger.trace(`[app/getAccountFolder][get folder data is ${JSON.stringify(data)}]`);
    data.subFolders.sort((aItem, bItem) => {
      // 重要文件夹排序仅次于收件箱
      if (aItem.type != FolderType.inbox && bItem.type == FolderType.important) {
        return 1
      }
      return aItem.type - bItem.type
    })
    return {
      account: account,
      rootFolder: data
    }
  }

  /**
   * 更新一个账号所有文件夹树
   * eg:更新所有文件夹的未读数.
   * @param account
   * @returns
   */
  async updateAccountFolder(account: string,needRefresh:boolean = false): Promise<void> {
    const engine = this._engineMap.get(account)
    const root = await engine.getRootFolder();
    unreadLogger.trace(`[app/updateAccountFolder][get folder data is ${JSON.stringify(root.getFullName())}]`);
    const data = await getFolderData(root,needRefresh)
    unreadLogger.trace(`[app/updateAccountFolder][get folder data is ${JSON.stringify(data)}]`);
    data.subFolders.sort((aItem, bItem) => {
      // 重要文件夹排序仅次于收件箱
      if (aItem.type != FolderType.inbox && bItem.type == FolderType.important) {
        return 1
      }
      return aItem.type - bItem.type
    })
    // AccountFolder
    DataStorage.updateAccountFolderTree(account,data);
  }

  async getAccountFolders(accounts: string[]): Promise<AccountFolder[]> {
    let folders: AccountFolder[] = []
    for (const a of accounts) {
      this.getAccountFolder(a)
        .then(f => {
          folders.push(f)
          logger.trace(`[folder list info ${JSON.stringify(f)}]`)
          DataStorage.setAccountFolders(folders)
        })
    }
    return folders
  }

  async deleteFolder(account: string, folder: string): Promise<void> {
    const f = await this.getFolder(account, folder)
    const subFolders = await f.getSubFolders()
    if (subFolders.length > 0) {
      return Promise.reject("无法删除此文件夹。请先清空此文件夹中的子目录和邮件，再进行删除文件夹操作")
    }
    const mailCount = await f.getMailCount()
    if (mailCount > 0) {
      return Promise.reject("无法删除此文件夹。请先清空此文件夹中的子目录和邮件，再进行删除文件夹操作")
    }
    let parentFolder: IFolder = await this.getParentFolder(account, folder)
    const folderName = folder.split("/").pop()
    await parentFolder.deleteSubFolder(folderName)
    if (parentFolder.getType() == FolderType.root) {
      DataStorage.deleteStoreFolder(account, null, folder)
    } else {
      DataStorage.deleteStoreFolder(account, parentFolder.getFullName(), folder)
    }
  }

  async createFolder(account: string, parentFolder: string | null, newFolderName: string): Promise<void> {
    let parentF: IFolder = await this.getFolder(account, parentFolder)
    let fullFolderName = newFolderName
    if (parentFolder) {
      fullFolderName = `${parentFolder}/${newFolderName}`
    } else {
      fullFolderName = newFolderName
    }
    await parentF.createSubFolder(newFolderName)
    DataStorage.addStoreFolder(account, parentFolder, fullFolderName)
  }

  async renameFolder(account: string, folder: string, newName: string): Promise<void> {
    const f = await this.getFolder(account, folder)
    let fullName = f.getFullName()
    let nameConmponets = fullName.split("/")
    nameConmponets[nameConmponets.length - 1] = newName
    const newFullName = nameConmponets.join("/")
    await f.renameFolder(newFullName)
    DataStorage.renameStoreFolder(account, folder, newFullName)
  }

  async getMailList(account: string, folder: string, start: number, size: number, sortType: 'date' | 'size', desc: boolean): Promise<MailSummary[]> {
    logger.debug("getMailList", '****' + account.substring(4), folder, sortType, desc)
    try {
      const mailSummaries = await this.innerGetMails(account, folder, null, size, sortType, desc)
      if (folder != DataStorage.currentFolder()) {
        return;
      }
      DataStorage.resetStoreMails(mailSummaries)
      DataStorage.setMailSort(sortType, desc)
      // 设置未读数不要阻塞邮件列表流程
      this.getFolder(account, folder)
        .then(f => {
          return f.getUnreadMailCount()
        })
        .then(unreadCount => {
          DataStorage.setCurrentFolderUnread(unreadCount)
        })

      const mids = mailSummaries.map(m => m.mid)
      this.getContentSummary(account, folder, mids)
      return mailSummaries
    } catch (e) {
      logger.debug("failed", e, '****' + account.substring(4), folder, start, size)
      logger.error("getMailList failed", folder, start)
    }
  }

  async getMailsAfter(account: string, folder: string, mailId: string, size: number, sortType: 'date' | 'size', desc: boolean): Promise<MailSummary[]> {
    logger.debug("getMailsAfter", '****' + account.substring(4), folder, mailId, sortType, desc)
    const f = await this.getFolder(account, folder)
    try {
      const mailSummaries = await this.innerGetMails(account, folder, mailId, size, sortType, desc)
      DataStorage.appendMails(mailSummaries)

      const mids = mailSummaries.map(m => m.mid)
      this.getContentSummary(account, folder, mids)
      return mailSummaries
    }
    catch (e) {
      logger.error("get mails after failed", e, folder, mailId)
    }
  }

  // 实际内部调用请求指定邮件列表数据方法
  private async innerGetMails(account: string, folder: string, mailId: string | null, size: number, sortType: 'date' | 'size', desc: boolean): Promise<MailSummary[]> {
    const f = await this.getFolder(account, folder)
    const mails = await f.getMailList(size, mailId, { by: sortType, desc: desc }).catch(err => {
      return [];
    })
    if (mails.length === 0) {
      return [];
    }
    logger.info("mails", mails.length);
    return this.getMailSummaries(f, mails)
    // return this.getMailSummaries(f, [mails[3]])
  }

  // 获取邮件摘要信息
  private async getMailSummaries(folder: IFolder, mails: IMail[]): Promise<MailSummary[]> {
    const p = Promise.all(mails.map(async mail => {
      let from = await mail.getFrom()
      if (folder.getType() == FolderType.sent || folder.getType() == FolderType.draft) {
        from = await new Promise(async (resolve, reject) => {
          mail.getTo()
            .then(to => {
              if (to.length > 0) {
                resolve(to[0])
              } else {
                return mail.getCc()
              }
            })
            .then(cc => {
              if (cc.length > 0) {
                resolve(cc[0])
              } else {
                return mail.getBcc()
              }
            })
            .then(bcc => {
              if (bcc.length > 0) {
                resolve(bcc[0])
              } else {
                resolve({ name: "无收件人", email: "" })
              }
            })
        })
        const receiver = from.name || from.email
        if (receiver.length == 0) {
          from.name = "无收件人"
        }
      }
      let folderFullName = folder.getFullName()
      if (folder.getType() == FolderType.important) {
        folderFullName = mail.getFolder().getFullName()
      }
      return {
        mid: mail.getId(),
        account: StringUtil.isNullOrEmpty(from.email) ? from.name : from.email,
        subject: await mail.getSubject(),
        summary: "", //获取摘要导致邮件数据返回慢，暂时先屏蔽获取方法 await mail.getDigest(10),
        date: await mail.getDate(),
        isFlagged: await mail.isFlagged(),
        isNew: !(await mail.isSeen()),
        hasAttachment: (await mail.getAttachmentInfoList()).length > 0,
        folder: folderFullName
      }
    }).map(p => p.catch(e => null)));

    return p.then((arr: (MailSummary | null)[]) => arr.filter(v =>!!v));
  }

  private async getContentSummary(account: string, folder: string, mailIdList: string[]) {
    const f = await this.getFolder(account, folder)
    let summaryMap: Map<string, string> = new Map()
    for (const mid of mailIdList) {
      const p: Promise<string> = new Promise(async (resolve, reject) => {
        f.getMail(mid)
          .then(m => m.getDigest(10))
          .then(summary =>  resolve(summary))
          .catch(error => reject(error))
      })
      try {
        const summary = await p
        summaryMap.set(mid, summary)
      } catch (e) {
        summaryMap.set(mid, "")
      }
    }
    DataStorage.updateSummary(summaryMap)
  }

  async refresh(account: string): Promise<void> {

  }

  /**
   * 仅仅移动邮件数据.
   * @param account
   * @param fromFolder
   * @param mailIdList
   * @param toFolder
   * @returns
   */
  async doMoveMail(account: string, fromFolder: string, mailIdList: string[], toFolder: string): Promise<string[]> {
    const sourceFolder = await this.getFolder(account, fromFolder)
    const targetFolder = await this.getFolder(account, toFolder)
    // await Promise.all(mailIdList.map(async mid => {
    //   const mail = await sourceFolder.getMail(mid)
    //   return mail.moveTo(targetFolder)
    // }))
    for (const mid of mailIdList) {
      const mail = await sourceFolder.getMail(mid)
      logger.debug(`[__imap] [doMoveMail] [${fromFolder}=>${toFolder} ${mid}]`)
      await mail.moveTo(targetFolder)
    }
    return []
  }

  /**
   * 移动邮件并刷新界面.
   * @param account
   * @param fromFolder
   * @param mailIdList
   * @param toFolder
   * @returns
   */
  async moveMail(account: string, fromFolder: string, mailIdList: string[], toFolder: string): Promise<string[]> {
    await this.doMoveMail(account,fromFolder,mailIdList,toFolder);

    let currentFolder = await getApp().getCurrentFolder(account);
    // let impFolder = await this.getImpFolder(account);
    if(currentFolder.getType() == FolderType.important) {
      //如果是imp文件夹,需要更新ui数据而不是删除.
      this.updateMails(mailIdList,"folder",toFolder);
    }
    else {
      //删除当前列表的数据.
      if (currentFolder.getFullName() == fromFolder) {
        logger.trace(`[__draft][moveMail][delete mail from current list:${JSON.stringify(mailIdList)}`)
        DataStorage.deleteMails(mailIdList)
      }
    }

    this.updateFolderTreeCount(account,fromFolder);
    this.updateFolderTreeCount(account,toFolder);
    this.updateCurrentFolderCount(account);


    // TODO: 待SDK返回mid
    return []
  }
  /**
   * 更新当前DataStorage中文件夹树中,对应文件夹的未读数.
   * @param account
   * @param folder
   */
  async updateFolderTreeCount(account:string,folderFullName:string): Promise<void> {
    const folder = await this.getFolder(account, folderFullName);
    let newUnreadCount = await folder.getUnreadMailCount();
    DataStorage.updateFolderTreeUnreadBySet(account,folderFullName,newUnreadCount);
  }

  /**
   * 更新ui中当前文件夹的未读数.
   * @param account
   */
  async updateCurrentFolderCount(account:string): Promise<void> {
    const currentFolder = DataStorage.currentFolder();
    let ifolder = await this.getFolder(account, currentFolder);
    await ifolder.refresh();
    let newUnreadCount = await ifolder.getUnreadMailCount();
    DataStorage.updateCurrentFolderUnreadBySet(newUnreadCount);
  }


  /**
   * 为了兼容重要文件夹的删除操作增加的接口.
   * @param account
   * @param mails
   * @returns
   * 首先将要删除的邮件归好类,然后针对每个文件夹去删除.
   *
   */
  async deleteMails(account: string, mails: MailSummary[]): Promise<void> {
    let map: Map<string, string[]> = new Map();
    mails.forEach((item) => {
      let mids = map.get(item.folder);
      if (!mids) {
        let newMids: string[] = [];
        newMids.push(item.mid);
        map.set(item.folder, newMids);
      }
      else {
        mids.push(item.mid);
      }
    })

    map.forEach(async (mids,folder)=>{
      //移动或者删除文件夹,
      await this.deleteMail(account,folder,mids);
      await this.updateFolderTreeCount(account,folder);
    })
    //更新回收站未读数.
    let trash = await this.getTrashFolder(account);
    if(trash) {
      await this.updateFolderTreeCount(account,trash.getFullName());
    }
    //更新重要邮件未读数.
    await this.updateImportantFolderUnread(account);
    //更新当前文件节的未读数.
    await this.updateCurrentFolderCount(account);
  }

  /**
   * 为了兼容重要文件夹的删除操作增加的接口.
   * @param account
   * @param mails
   * @returns
   * 首先将要删除的邮件归好类,然后针对每个文件夹去删除.
   *
   */
  async markMails(account: string, mails: MailSummary[],flagType: 'new' | 'flagged', newValue: boolean): Promise<void> {
    let map: Map<string, string[]> = new Map();
    mails.forEach((item) => {
      let mids = map.get(item.folder);
      if (!mids) {
        let newMids: string[] = [];
        newMids.push(item.mid);
        map.set(item.folder, newMids);
      }
      else {
        mids.push(item.mid);
      }
    })

    map.forEach(async (mids,folder)=>{
      await this.markMail(account,folder,mids, flagType,newValue);
    })
  }

  /**
   * 为了兼容重要文件夹的删除操作增加的接口.
   * @param account
   * @param mails
   * @returns
   * 首先将要删除的邮件归好类,然后针对每个文件夹去删除.
   *
   */
  async moveMails(account: string, mails: MailSummary[],toFolder:string): Promise<void> {
    let map: Map<string, string[]> = new Map();
    mails.forEach((item) => {
      let mids = map.get(item.folder);
      if (!mids) {
        let newMids: string[] = [];
        newMids.push(item.mid);
        map.set(item.folder, newMids);
      }
      else {
        mids.push(item.mid);
      }
    })

    map.forEach(async (mids,folder)=>{
      await this.moveMail(account,folder,mids,toFolder);
    })
  }




  /**
   * 邮件标志位发生更新，或者文件夹信息变更,同步更新LocalStore存储的邮件列表
   * ui界面更新.
   * 从currentMails中,找到匹配的项,更新对应的值,生成新的MailSummary让ui更新.
   * @param mailIds 需要更新的邮件id数组
   * @param flag 更新的标志位类型
   * @param newValue 标志位新值
   * @param newFolder 新的文件夹,如果为null表示采用旧的文件夹不动.
   */
    updateMails(mailIds: string[], flag: 'new' | 'flagged'| 'folder', newValue:string): void {
    return DataStorage.updateMails(mailIds,flag,newValue);
  }

  /**
   * 单个文件夹删除并更新ui.
   * @param account
   * @param folder
   * @param mailIdList
   * @returns
   */
   async deleteMail(account: string, folder: string, mailIdList: string[]): Promise<void> {
    const f = await this.getFolder(account, folder)
    if (f.getType() == FolderType.trash) {
      await this.doDeleteMail(account,f,mailIdList);
    }
    else {
      const trash = await this.getTrashFolder(account)
      let trashName = trash.getFullName();
      logger.debug(`[__imap][move ${folder}==>${trashName}]`)
      await this.moveMail(account, folder, mailIdList, trashName)
    }
  }

  /**
   * 单个文件夹的邮件删除并更新ui
   * 将folder从垃圾箱删除.
   * folder必须为垃圾箱
   * mailIdList必须都为一个文件夹的数据.
   * @param account
   * @param folder
   * @param mailIdList
   * @returns
   */
  async doDeleteMail(account: string, folder: IFolder, mailIdList: string[]): Promise<void> {

    if(folder.getType() != FolderType.trash) return;

    await Promise.all(mailIdList.map(async mid => {
      const mail = await folder.getMail(mid)
      return mail.delete()
    })).catch((e:unknown)=>{
      logger.warn(`[__pop3] [delete mail exception]`)
    })

    //当前是imp文件夹,或者当前就是trash文件夹,需要删除当前列表的选中项目.
    let currentFolder = await this.getCurrentFolder(account);
    if(currentFolder.getType() == FolderType.important || currentFolder.getFullName() == folder.getFullName()) {
        logger.trace(`[__draft][moveMail][delete mail from current list:${JSON.stringify(mailIdList)}`)
        DataStorage.deleteMails(mailIdList);
    }
    await this.updateCurrentFolderCount(account);
    await this.updateFolderTreeCount(account,currentFolder.getFullName());
    let trash = await this.getTrashFolder(account)!;
    if(trash) {
      await this.updateFolderTreeCount(account,trash.getFullName());
    }
  }

  /**
   * 获取当前进入的文件夹IFolder
   * @param account
   * @returns
   */
  async getCurrentFolder(account:string): Promise<IFolder> {
    const folderName = DataStorage.currentFolder()
    const currentFolder =  await this.getFolder(account,folderName)
    return currentFolder;
  }

  /**
   * 单个文件夹的mark操作,并更新ui.
   * @param account
   * @param folder
   * @param mailIdList
   * @param flagType
   * @param newValue
   * @returns
   */
  async markMail(account: string, folder: string, mailIdList: string[], flagType: 'new' | 'flagged', newValue: boolean): Promise<void> {
    const f = await this.getFolder(account, folder)
    let unreadIncrease: number = 0 // 未读数变动增量(正数则未读数增加，负数则未读数减少)
    await Promise.all(mailIdList.map(async mid => {
      const mail = await f.getMail(mid)
      if (flagType == 'new') {
        let seen = await mail.isSeen()
        if (!seen != newValue) {
          await mail.setSeen(!newValue)
          seen = !seen
          if (seen) {
            unreadIncrease -= 1
          } else {
            unreadIncrease += 1
          }
        }
      } else if (flagType == 'flagged') {
          let flagged = await mail.isFlagged()
        if (flagged != newValue) {
          await mail.setFlagged(newValue)
          flagged = !flagged
        }
      }
    }))
    if (flagType == "new") {
      if (unreadIncrease != 0) {
        //更新文件夹树中的未读数
        await this.updateFolderTreeUnread(account,f);
      }
    }
    const currentMid = DataStorage.currentMailId()
    if (mailIdList.findIndex(mid => mid == currentMid) != -1) {
      DataStorage.changeCurrentReadMailFlag(flagType)
    }
    const currentFolderType = DataStorage.currentFolderType()

    // 在重要文件夹中取消重要标记，需要从列表中移除
    if (currentFolderType == FolderType.important && flagType == 'flagged' && newValue == false) {
      DataStorage.deleteMails(mailIdList)
    } else {
      DataStorage.updateMails(mailIdList, flagType, newValue)
    }
    //更新重要文件夹以及当前文件夹未读数.
    await this.updateCurrentFolderCount(account);
    await this.updateImportantFolderUnread(account);
  }

  async updateFolderTreeUnread(account:string,folder:IFolder|string): Promise<void> {
    let ifolder = (typeof folder == "string")?await this.getFolder(account,folder) :folder
    await ifolder.refresh();
    let unreadCount:number = await ifolder.getUnreadMailCount()
    let folderFullName = ifolder.getFullName();
    logger.trace(`[__imap] [update folderList ${folderFullName} ${unreadCount}]`)
    DataStorage.updateFolderTreeUnreadBySet(account,ifolder.getFullName(),unreadCount);
  }

  async updateImportantFolderUnread(account:string): Promise<void> {
      let impFolder = await this.getImpFolder(account)
      await impFolder.refresh();
      DataStorage.updateFolderTreeUnreadBySet(account,impFolder.getFullName(), await impFolder.getUnreadMailCount());
  }

  /**
   * 解析源文
   * @WARN 得到的数据都是解码后的数据.
   *
   * @param content
   * @param fileUniqKey
   * @returns
   */
  async getMailByEmlContent(content: string, uri: string): Promise<MailItem | null> {
    if (content) {
      let parser = new EmlParser();
      try {
        let mime = await parser.mimeParse(content);
        let mail = new MimeMail(mime);
        let attachments = mail.getAttachments();
        let attachmentsPromise = Promise.all(attachments.map(async (item, index) => {
          const name = getMimePartName(item);
          return {
            name: name,
            type: item.contentType.type,
            subType: item.contentType.subType,
            id: `${index}`,
            encoding: item.encoding,
            size: item.size,
            index: index,
            localPath: await this.downloadPartIfNeeded((item as Mime), uri, false, name, "",`${index}`,item.contentType.subType)
          }
        }))
        let inlineImages = mail.getInlineImages();
        let inlineImagesPromise = Promise.all(inlineImages.map(async (item, index) => {
          const name = getMimePartName(item);
          return {
            name: name,
            type: item.contentType.type,
            subType: item.contentType.subType,
            id: item.contentId,
            encoding: item.encoding,
            size: item.size,
            index: index,
            localPath: await this.downloadPartIfNeeded((item as Mime), uri, true, name, "",`${item.contentId}`,item.contentType.subType)
          }
        }))

        const envelope = mail.getEnvelope();
        let to = envelope.to.filter((item) => {
          return (item.name.length > 0 || item.email.length > 0)
        });
        envelope.to = to;
        let cc = envelope.cc.filter((item) => {
          return (item.name.length > 0 || item.email.length > 0)
        });
        envelope.cc = cc;
        let bcc = envelope.bcc.filter((item) => {
          return (item.name.length > 0 || item.email.length > 0)
        });
        envelope.bcc = bcc;

        let htmlPart: Mime | undefined = mail.getHtmlPart() as Mime | undefined

        let html: string = "";
        if (htmlPart) {
          html = await ParseUtil.decodeMimePartToString(htmlPart);
        }

        let textPart: Mime | undefined = mail.getPlainPart() as Mime | undefined
        let text: string = "";
        if (textPart) {
          text = await ParseUtil.decodeMimePartToString(textPart);
        }

        let mailItem: MailItem = {
          from: envelope.from,
          to: envelope.to,
          cc: envelope.cc,
          bcc: envelope.bcc,
          subject: envelope.subject,
          date: envelope.date,
          html: html,
          text: text,
          size: content.length,
          attachments: await attachmentsPromise,
          inlineImages: await inlineImagesPromise,
          isFlagged: false
        }
        return mailItem;
      }
      catch (e) {
        logger.warn('1');
      }
    }

    return null;
  }

  async getMailByMsgContent(filePath: string, uri: string,account:string,folder:string): Promise<MailItem | null> {
    let mid: string = new Date().getTime().toString();
    let fileStat = await fs.stat(filePath);
    let readFile = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
    let size = fileStat.size;
    let buffer = new ArrayBuffer(size);
    fs.readSync(readFile.fd, buffer, { offset: 0, length: size });
    let msgMailEntry = new MsgReader(buffer).parse();

    let from: MailAddress = { name: msgMailEntry.from.name, email: msgMailEntry.from.email }
    let to: MailAddress [] = [];
    msgMailEntry.to.map((value) => {
      to.push({ name: value.name, email: value.email });
    })
    let cc: MailAddress [] = [];
    msgMailEntry.cc.map((value) => {
      cc.push({ name: value.name, email: value.email });
    })
    let bcc: MailAddress [] = [];
    msgMailEntry.bcc.map((value) => {
      bcc.push({ name: value.name, email: value.email });
    })

    let attach: Attachment [] = [];
    let inlineImages: Attachment [] = [];
    for (let i = 0; i < msgMailEntry.attachments.length; i++) {
      let item =  msgMailEntry.attachments[i];
    // for (let item of msgMailEntry.attachments) {
      let path = await this.saveArrayToFile(mid, item.value,
        item.name.split(".")[0],item.name.split(".")[1], false,i.toString(),account,folder);
      attach.push({
        name: item.name,
        type: item.mime,
        subType: item.mime,
        id: item.attachContentId,
        encoding: "",
        size: item.value.length,
        index: 0,
        localPath: path
      })
    }
    for (let item of msgMailEntry.inlineImages) {
      let path = await this.saveArrayToFile(mid, item.value,
        item.name.split(".")[0],item.name.split(".")[1], false,item.attachContentId,account,folder);
      inlineImages.push({
        name: item.name,
        type: item.mime,
        subType: item.mime,
        id: item.attachContentId,
        encoding: "",
        size: item.value.length,
        index: 0,
        localPath: path
      })
    }

    let mailItem: MailItem = {
      from: from,
      to: to,
      cc: cc,
      bcc: bcc,
      subject: msgMailEntry.subject,
      date: null,
      html: msgMailEntry.htmlBody,
      text: msgMailEntry.body,
      size: size,
      attachments: attach,
      inlineImages: inlineImages,
      isFlagged: false
    };
    return new Promise((resolve, reject) => {
      resolve(mailItem);
    });
  }

  /**
   * 将一个stream流存储为本地文件,返回其path.
   * @param stream
   * @param uri
   * @param index
   * @param isInline
   * @returns
   */
  private async downloadStreamIfNeeded(stream: IBuffer, uri: string, index: number, isInline: boolean, rawFileName: string, folderFullName: string,cid:string,fileType:string): Promise<string | null> {
    let relativePath = EmlFileManager.getRelativeDirPathForMailAttachment(null, uri, folderFullName, isInline);
    let fileName = EmlFileManager.getAttachNameWithRawName(cid,rawFileName, fileType)
    let fileRootDir = this._context.filesDir;
    let filePath = `${fileRootDir}/${relativePath}/${fileName}`
    let b = FileManager.isFileExisted(filePath);
    if (!b) {
      let content = await stream.readAll();
      try {
        FileManager.writeToFile(content, fileRootDir, relativePath, fileName);
      } catch (e) {
        logger.warn(e)
        return null
      }
    }
    return filePath;
  }


  /**
   * 下载一个part的数据,并解码,保存解码后的数据.
   * @param stream
   * @param uri
   * @param index
   * @param isInline
   * @param rawFileName
   * @param folderFullName
   * @param cid 内联图片传cid, 其余传index.
   * @param needDecode
   * @returns
   */
  private async downloadPartIfNeeded(part: Mime, uri: string, isInline: boolean, rawFileName: string, folderFullName: string,cid:string,fileType:string): Promise<string | null> {
    let relativePath = EmlFileManager.getRelativeDirPathForMailAttachment(null, uri, folderFullName, isInline);
    let fileName = EmlFileManager.getAttachNameWithRawName(cid, rawFileName,fileType)
    let fileRootDir = this._context.filesDir;
    let filePath = `${fileRootDir}/${relativePath}/${fileName}`
    let b = FileManager.isFileExisted(filePath);
    if (!b) {
      let content: Uint8Array = await ParseUtil.decodeMimePartToBlob(part);
      let arrayBuffer = content.buffer;
      try {
        FileManager.writeToFile(arrayBuffer, fileRootDir, relativePath, fileName);
      } catch (e) {
        logger.warn(e)
        return null
      }
    }
    return filePath;
  }


  private async saveArrayToFile(mid: string, array: Uint8Array, rawName: string,fileType:string, inline: boolean,cid:string,account:string,folder:string): Promise<string | null> {
    let arrayBuffer = array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset);
    const buff = new MemBuffer();
    buff.end(array);
    let path = await FileManager.saveFile(this._context, mid, buff, rawName,fileType, inline,cid,account,folder);
    return new Promise((resolve, reject) => {
      resolve(path);
    });
  }

  async getMail(account: string, folder: string, mailId: string): Promise<MailItem> {
    const isSearching = DataStorage.isSearching()
    if (!isSearching) { // 只记录非搜索场景下的mid
      DataStorage.setCurrentMailId(mailId)
    }
    const f = await this.getFolder(account, folder)
    const mail = await f.getMail(mailId)
    let attachments = await mail.getAttachmentInfoList()
    let attachmentsPromise = Promise.all(attachments.map(async (item, index) => {
      const name = getMimePartName(item);
      let uniqName = StringUtil.uniqName(index.toString(), name, item.contentType.subType)
      return {
        name: name,
        type: item.contentType.type,
        subType: item.contentType.subType,
        id: item.contentId,
        encoding: item.encoding,
        size: item.size * 0.731, // 【HMOS-645】SDK中获取文件大小为经过base64编码，粗略乘以四分之三作为实际大小
        index: index,
        localPath: await this.isLocalExistedAttachment(mailId, uniqName, false, this._context,account,folder)
      }
    }))

    let inlineImages = await mail.getInlineImageInfoList()
    let inlineImagesPromise = Promise.all(inlineImages.map(async (item, index) => {
      const name = getMimePartName(item);
      let uniqName = StringUtil.uniqName(item.contentId, name, item.contentType.subType)
      return {
        name: name,
        type: item.contentType.type,
        subType: item.contentType.subType,
        id: item.contentId,
        encoding: item.encoding,
        size: item.size,
        index: index,
        localPath: await this.isLocalExistedAttachment(mailId, uniqName, true, this._context,account,folder)
      }
    }))

    // 读信设置已读状态和未读数不阻塞主流程
    mail.isSeen()
      .then(seen => {
        if (!seen) {
          mail.setSeen(true)
          DataStorage.updateStoreFolderUnread(account, folder, -1)
          DataStorage.updateMails([mail.getId()], "new", false);
        }
      })

    let fromItem = await mail.getFrom()
    return {
      from: { name: fromItem.name, email: fromItem.email },
      to: (await mail.getTo()).map(toItem => {
        return { name: toItem.name, email: toItem.email }
      }),
      cc: (await mail.getCc()).map(toItem => {
        return { name: toItem.name, email: toItem.email }
      }),
      bcc: (await mail.getBcc()).map(toItem => {
        return { name: toItem.name, email: toItem.email }
      }),
      subject: await mail.getSubject(),
      date: await mail.getDate(),
      html: await mail.getHtml(),
      text: await mail.getPlain(),
      size: await mail.getSize(),
      attachments: await attachmentsPromise,
      inlineImages: await inlineImagesPromise,
      isFlagged: await mail.isFlagged()
    }
  }

  async getAttachments(account: string, folder: string, mailId: string): Promise<Attachment[]> {
    const f = await this.getFolder(account, folder)
    const mail = await f.getMail(mailId)
    let attachments = await mail.getAttachmentInfoList()
    return Promise.all(attachments.map(async (file, index) => {
      const name = getMimePartName(file);
      let uniqName = StringUtil.uniqName(index.toString(), name, file.contentType.subType)
      let localPath = await this.isLocalExistedAttachment(mailId, uniqName, false, this._context,account,folder)
      return {
        name: name,
        type: file.contentType.type,
        subType: file.contentType.subType,
        id: file.contentId,
        encoding: file.encoding,
        size: file.size  * 0.731, // 【HMOS-645】SDK中获取文件大小为经过base64编码，粗略乘以四分之三作为实际大小
        index: index,
        localPath: localPath
      }
    }))
  }

  async getInlineImages(account: string, folder: string, mailId: string): Promise<Attachment[]> {
    const f = await this.getFolder(account, folder)
    const mail = await f.getMail(mailId)
    let inlineImgs = await mail.getInlineImageInfoList()
    return Promise.all(inlineImgs.map(async (file, index) => {
      const name = getMimePartName(file);
      let uniqName = StringUtil.uniqName(file.contentId || index.toString(), name, file.contentType.subType)
      let localPath = await this.isLocalExistedAttachment(mailId, uniqName, true, this._context,account,folder)
      if (!localPath) {
        localPath = await this.innerDownload(mail, mailId, index, true, file,account,folder)
      }
      return {
        name: name,
        type: file.contentType.type,
        subType: file.contentType.subType,
        id: file.contentId,
        encoding: file.encoding,
        size: file.size,
        index: index,
        localPath: localPath
      }
    }))
  }

  async download(account: string, folder: string, mailId: string, index: number, inline: boolean): Promise<string> {
    const f = await this.getFolder(account, folder)
    const mail = await f.getMail(mailId)
    if (inline) {
      let imgs = await mail.getInlineImageInfoList()
      return this.innerDownload(mail, mailId, index, inline, imgs[index],account,folder)
    } else {
      let attachments = await mail.getAttachmentInfoList()
      const localPath = await this.innerDownload(mail, mailId, index, inline, attachments[index],account,folder)
      const currentMid = DataStorage.currentMailId()
      // 确保是对当前读信进行下载操作，防止从列表或者其他可能进行下载触发标志位修改
      if (currentMid == mailId) {
        DataStorage.currentReadMailAttachmentDownloaded()
      }
      return localPath
    }
  }

  // 实际内部下载方法
  private async innerDownload(mail: IMail, mid: string, index: number, inline: boolean, file: MailStructure,account:string,folder:string): Promise<string | null> {
    const name = getMimePartName(file);
    // let uniqName = StringUtil.uniqName(file.contentId || index.toString(), name, file.contentType.subType)
    let stream: IBuffer
    if (inline) {
      stream = await mail.getInlineImage(index)
    } else {
      stream = await mail.getAttachment(index)
    }

    // return (stream as FileBuffer).filePath

    /*
    let u8a:Uint8Array = new Uint8Array();
    for await (const chunk of stream.readRaw()) {
      let mergeArr = new Uint8Array(u8a.length + chunk.length);
      mergeArr.set(u8a);
      mergeArr.set(chunk, u8a.length);
      u8a = mergeArr;
    }
    */
    let fileTitle = name.split(".")[0];
    let fileType = name.split(".")[1];
    try {
      let localPath = await FileManager.saveFile(this._context, mid, stream, fileTitle,fileType, inline, inline?file.contentId:index.toString(),account,folder)
      return localPath
    } catch (e) {
      // TODO: 是否应该是reject
      logger.warn(e)
      return null
    }
  }

  async search(account: string, keyword: string, types: ('subject' | 'attachment' | 'from' | 'to')[], start: number, size: number): Promise<MailSummary[]> {
    DataStorage.setSearching(true)
    const engine = this._engineMap.get(account)
    const mails = await engine.search(keyword, types, start, size)
    return Promise.all(mails.map(async mail => {
      let from = await mail.getFrom()
      let folder = mail.getFolder().getName()
      if (folder == "Sent" || folder == "Sent Items" || folder == "Sent Messages" || folder == "已发送") {
        let to = await mail.getTo()
        if (to.length > 0) {
          from = to[0]
        }
      }
      return {
        mid: mail.getId(),
        account: StringUtil.isNullOrEmpty(from.email) ? from.name : from.email,
        subject: await mail.getSubject(),
        summary: "", //获取摘要导致邮件数据返回慢，暂时先屏蔽获取方法 await mail.getDigest(10),
        date: await mail.getDate(),
        isFlagged: await mail.isFlagged(),
        isNew: !(await mail.isSeen()),
        hasAttachment: (await mail.getAttachmentInfoList()).length > 0,
        folder: folder
      }
    }))
  }

  async sendMail(account: string, to: MailAddress[], cc: MailAddress[],
                 bcc: MailAddress[], subject: string, content: string,
                 inlineImages: InlineAttachment[],
                 attachments: Attachment[],
                 isPlain: boolean): Promise<void> {

    const engine = this._engineMap.get(account);
    const transport = engine.getTransport();
    try {
      return await transport.sendMail({ name: '', email: account },
        to, cc, bcc,
        subject,
        content,
        "",
        inlineImages.map((image) => {
          return {
            fileName: getFileName(image.file),
            content: new FileBuffer(image.file),
            contentId: image.contentId
          }
        }),
        attachments.map(attach => ({
          fileName: attach.name,
          content: new FileBuffer(attach.localPath)
        }))
      );
    }
    catch (e) {
      logger.error('error', e)
      return new Promise((resolve, reject) => {
        if (e instanceof CMError) {
          reject(ErrorUtil.codeToMsg(e.code))
        } else {
          reject("发信失败")
        }
      })
    }
  }

  /**
   * 草稿箱邮件已发送之后的处理.
   * imap是删除该草稿.
   * pop3是移动到已发送.
   * @param draftId
   * @param account
   * @param originDraftId  有可能草稿箱的mid变了,originDraftId是用来删除界面上的数据的.
   * @returns
   */
  async onDraftSent(draftId: string, account: string, originDraftId: string): Promise<void> {
    logger.info("saveToDraft delete:" + draftId);
    if (draftId == null || draftId == "") {
      return;
    }
    const engine = this._engineMap.get(account);
    let draft = await engine.getDraft();
    if (engine._properties.imap) {
      // folder.getMail(draftId).then(mail => {
      //   return mail.delete();
      // })
      this.deleteMail(account, draft.getName(), [draftId]);
    }
    else {
      let sent = await engine.getSent();
      // folder.getMail(draftId).then(mail => {
      //   return mail.moveTo(folder);
      // })
      // 这里移动的可能是draft的新mid,但是界面上需要更新的是旧的
      this.moveMail(account, draft.getName(), [draftId], sent.getName());
    }
    //删除界面上的数据.
    DataStorage.deleteMails([originDraftId])
  }

  async deleteDraft(draftId: string, account: string): Promise<void> {
    logger.info("saveToDraft delete:" + draftId);
    if (draftId == null || draftId == "") {
      return;
    }
    const engine = this._engineMap.get(account);
    let folder = await engine.getDraft();
    folder.getMail(draftId).then(mail => {
      return mail.delete();
    })

  }

  async saveDraft(draftId: string, account: string, to: MailAddress[], cc: MailAddress[],
                  bcc: MailAddress[], subject: string, content: string,
                  inlines: {
                    file: string,
                    contentId: string
                  }[], attachments: string[],
                  isPlain: boolean): Promise<string> {

    logger.trace("saveToDraft", `draftId${draftId}  subject:${subject}  content:${content}`)

    let _from: EmailAddress = { name: "", email: account };
    let toList: EmailAddress[] = to.map((item) => {
      return { name: item.name, email: item.email }
    })
    let ccList: EmailAddress[] = cc.map((item) => {
      return { name: item.name, email: item.email }
    })
    let bccList: EmailAddress[] = bcc.map((item) => {
      return { name: item.name, email: item.email }
    })
    let mail = await serializeMail(
      {
        from: _from,
        to: toList,
        cc: ccList,
        bcc: bccList,
        subject
      },
      new Map([
        ["date", { value: (new Date()).toUTCString() }]
      ]),
      content,
      "",
      inlines.map((image) => {
        return {
          name: getFileName(image.file),
          body: new FileBuffer(image.file),
          contentId: image.contentId,
          contentType: guessContentType(getFileName(getFileName(image.file))),
          type: "",
          subType: "",
          size: 1
        }
      }),
      attachments.map((attach) => {
        return {
          name: getFileName(attach),
          body: new FileBuffer(attach),
          contentType: guessContentType(getFileName(getFileName(attach))),
          type: "",
          subType: "",
          size: 1
        }
      })
    );
    const engine = this._engineMap.get(account);
    let folder = await engine.getDraft();
    let _storage = (engine.getStore() as SyncStore)._storage;
    const buff = (engine.getStore() as SyncStore)._bufferCreator.createBuffer({});
    buff.end(mail);
    if (draftId == null || draftId == "") {
      draftId = util.generateRandomUUID();
    } else {
      await _storage.deleteMail(folder.getFullName(), draftId);
    }
    await _storage.addMail(folder.getFullName(), draftId, buff);
    (folder as Folder)._mailCache.remove(draftId);
    return draftId;
  }

  async getFullMail(account: string, folder: string, mailId: string): Promise<MailItem> {
    let mail = await this.getMail(account, folder, mailId)
    const inlineImges = await this.getInlineImages(account, folder, mailId)
    mail.inlineImages = inlineImges
    const attachments = await Promise.all(mail.attachments.map(async (att, index) => {
      const localPath = await this.download(account, folder, mailId, index, false)
      att.localPath = localPath
      return att
    }))
    mail.attachments = attachments
    return mail
  }

  /**
   * 根据名字获取对应的文件夹
   * @param account
   * @param folderName 文件夹名称，根目录为null
   * @returns 如若匹配，怎返回匹配名字的文件夹，否则返回null
   */
  private async getFolder(account: string, folderName: string | null): Promise<IFolder | null> {
    const engine = this._engineMap.get(account)
    return engine.getFolder(folderName).catch((err): IFolder | null => {
      logger.error("get folder failed", folderName);
      return null;
    });
  }

  /**
   * 根据名字获取对应的父文件夹
   * @param account 账号邮箱
   * @param folder 文件夹名字
   * @returns 如果匹配存在，返回对应的父文件夹，否则返回null
   */
  private async getParentFolder(account: string, folder: string): Promise<IFolder | null> {
    const engine = this._engineMap.get(account)
    let components = folder.split("/")
    let parentFolder: IFolder
    let rootFolder = await engine.getRootFolder()
    if (components.length >= 2) {
      components.pop()
      let parentFolderName = components.join("/")
      parentFolder = await this.getFolder(account, parentFolderName)
    } else {
      parentFolder = rootFolder
    }
    return parentFolder
  }

  /**
   * 获取账号中的已删除文件夹
   * @param account 账号邮箱
   * @returns 返回已删除文件夹，如果不存在返回null
   */
   async getTrashFolder(account: string): Promise<IFolder | null> {
    const engine = this._engineMap.get(account)
    const rootFolder = await engine.getRootFolder()
    const subFolders = await rootFolder.getSubFolders()
    return subFolders.find(f => f.getType() == FolderType.trash)
  }


  /**
   * 获取账号中的已删除文件夹
   * @param account 账号邮箱
   * @returns 返回已删除文件夹，如果不存在返回null
   */
   async getImpFolder(account: string): Promise<IFolder | null> {
    const engine = this._engineMap.get(account)
    const rootFolder = await engine.getRootFolder()
    const subFolders = await rootFolder.getSubFolders()
    return subFolders.find(f => f.getType() == FolderType.important)
  }

  /**
   * 判断指定邮件的指定附件是否存在于本地
   * @param msgNO 指定邮件索引值
   * @param attachmentIndex 指定邮件的指定附件索引值
   * @param name 附件名称, 经过变换后的文件名
   * @param context 上下文
   * @returns 如果存在则返回本地路径，否则返回null
   * @discuss 目前attachmentIndex没用到，认为对于单一邮件中，文件名应该是唯一的
   */
  async isLocalExistedAttachment(msgNO: string, name: string, inline: boolean, context: common.Context,account:string,folderName:string): Promise<string | null> {
    let localPath = await FileManager.isExisted(context, msgNO, name, inline,account,folderName)
    return localPath
  }

  /**
   * 调用系统应用打开附件
   * @param localPath 附件本地路径
   * @param context 上下文
   */
  openAttachmentByOtherApp(localPath: string, context: common.UIAbilityContext): void {
    AttachmentUtil.openByOtherApp(context, localPath)
  }

  /**
   * 将已下载附件保存到系统文件管理器
   * @param mailId 附件所属邮件的索引值
   * @param fileId 文件id
   * @param fileName 文件名字
   * @param type 文件类型：img | auv | 其他类型
   * @param subType 文件后缀名
   * @param context 上下文
   */
  async saveToSystemFileManager(mailId: string, fileId: string, fileName: string, type: string, subType: string,account:string,folderName:string): Promise<void> {
    let uniqName = StringUtil.uniqName(fileId, fileName, subType)
    let localPath = await FileManager.isExisted(this._context, mailId, uniqName, false,account,folderName)
    if (localPath) {
      AttachmentUtil.saveToSystemFileManager(localPath, type, fileName)
    }
  }

  /**
   * 设置
   * @param interval 周期时长
   */
  async setIntervalToSync(interval: number): Promise<void> {
    await new AccountPreference().saveData(this._context, BaseConstants.KEY_SYNC, interval.toString())
    this._engineMap.forEach((engine) => {
      engine.setSyncInterval(interval)
    })
  }

  /**
   * 取出
   * @returns 间隔时长
   */
  async getSyncGapInterval(): Promise<number> {
    let data = await new AccountPreference().getValue(this._context, BaseConstants.KEY_SYNC)
    if (data) {
      return Number.parseInt(data as string)
    }
  }

  async syncAllAccountMails(): Promise<void> {
    this._engineMap.forEach(engine => {
      // 同步完成，新邮件会回调on('new-mail')
      // TODO: 待sdk处理同步后发现移动和删除邮件的情况
      engine.sync()
    })
  }

  // MARK: - DataStorage相关
  /**
   * 更改选中的账号或文件夹
   * @param account 待更改的账号
   * @param folder 待更改的文件夹
   */
  async changeSelectFolder(account: string, folder: string): Promise<void> {
    const f = await this.getFolder(account, folder)
    DataStorage.changeSelectFolder(account, folder, f.getType().valueOf())
  }

  /**
   * 退出搜索
   */
  endSearch(): void {
    DataStorage.setSearching(false)
  }


  isPop3(account: string): boolean {
    return this._engineMap.get(account)._properties.pop3 != null
  }


  /**
   * 从草稿箱返回,需要更新ui当前列表中,当前草稿的信息,因为mid已经变了.
   * @param account
   * @param folder
   * @param oldDraftMailId
   * @param newDraftMailId
   */
  async updateOneDraftInDraftListUi(account: string, folder: string, oldDraftMailId: string, newDraftMailId: string, needSort: boolean = false): Promise<void> {
    let mailsLink: SubscribedAbstractProperty<MailSummary[]> = appStorage.link('CurrentMails')
    const f = await this.getFolder(account, folder)
    const mail = await f.getMail(newDraftMailId);
    logger.trace(`[__draft] update draft ${oldDraftMailId}==>${newDraftMailId}`)
    let newSummarys = await this.getMailSummaries(f, [mail])
    let newSummary = newSummarys[0];
    if (newSummary) {
      let oldSummarys = mailsLink.get();
      let oldSummaryIndex = oldSummarys.findIndex((item) => {
        return item.mid == oldDraftMailId;
      })
      oldSummarys[oldSummaryIndex] = newSummary;

      if(needSort) {
        oldSummarys.sort( (a,b) => {
          return (a.date.getTime() - b.date.getTime()) < 0 ? 1 : -1
        });
      }

      mailsLink.set(oldSummarys);
      DataStorage.setCurrentMailId(newDraftMailId);
    }
    else {
      logger.trace(`[__draft] not found draft ${newDraftMailId}`)
    }
  }

  /**
   * ui界面上添加新草稿的数据.
   * @param account
   * @param folder
   * @param oldDraftMailId
   * @param newDraftMailId
   */
  async addNewDraftUi(account: string, folder: string, draftMailId: string): Promise<void> {
    let mailsLink: SubscribedAbstractProperty<MailSummary[]> = appStorage.link('CurrentMails')
    const f = await this.getFolder(account, folder)
    const mail = await f.getMail(draftMailId);
    let newSummarys = await this.getMailSummaries(f, [mail])
    if (newSummarys && newSummarys.length > 0) {
      DataStorage.addNewMails(account, folder, newSummarys)
    }
    else {
      logger.trace(`[__draft] not found draft ${draftMailId}`)
    }
  }
}

let app: MailApp | undefined;

// let t: TcpSocketTest|undefined ;

export function getApp(): MailApp {
  /*
if (!t) {
  t = new TcpSocketTest();
  t.sendManyData(t.serverHost2, t.serverPort2);
  t.connectTest(t.serverHost, t.serverPort);
  t.connectTest(t.serverHost, t.serverPort).then(() => {
    t.connectTest(t.serverHost2, t.serverPort2);

  });
  }
  * */
  if (!app) {
    app = new MailApp();
  }
  return app;
}


export type UIFolderItem = Omit<FolderItem, 'subFolders'> & {
  level: number,
  hasChildren: boolean
}

/**
 * 1,folderItem中的subFolders值没有的,需要进行一次解析.
 * @param folderItem
 * @param level
 * @param result
 */
export function transformFolderData(folderItem: FolderItem, level: number, result: UIFolderItem[]): void {
  result.push({
    name: folderItem.name,
    unreadCount: folderItem.unreadCount,
    type: folderItem.type,
    level: level,
    hasChildren: folderItem.subFolders.length > 0
  })
  if (folderItem.subFolders.length > 0) {
    for (let fi of folderItem.subFolders) {
      transformFolderData(fi, level + 1, result);
    }
  }
}
